(function() {

	// animation types
	var animationTypes = {
		line: function(progress) { 
			return progress
		},
		speed: function (progress) { 
			return Math.pow(progress, 3)
		},
		bounce: function (progress) {
			for(var a = 0, b = 1; ; a += b, b /= 2) {
				if (progress >= (7 - 4 * a) / 11)
					return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2);
			}
		},
		elastic: function (progress) {
			return Math.pow(2,10 * (progress - 1)) * Math.cos(20 * progress * Math.PI * 2.0 / 3);
		}
	};

	/**
	 * Animation direction
	 * @param delta
	 */
	function Direction(delta) {

		// on end
		this.out = function (progress) {
			return 1 - delta(1 - progress);
		};

		// on start and end
		this.inOut = function (progress) {
			if (progress < 0.5)
				return delta(2 * progress) / 2;
			else
				return (2 - delta(2 * (1 - progress))) / 2;
		}
	}

	/**
	 * Object animator
	 * @param name
	 */
	function Animator(name) {

		var handler;

		// choose a handler
		if (name != "xopacity")
			handler = "pixelation";
		else
			handler = name;

		// select action
		this.run = {
			// change CSS
			pixelation: function(val, obj) {
				obj.css(name, val + "px");
			},
			// change Opacity
			xopacity: function(val, obj) {
				obj.css("xopacity", val);
			}
			/// end of object
		}[handler];

	}

	var pool = {
		garbage: {},
		count: 0
	};

	var threadHandle;

	// Jim Dog plugin ANIMATE
	jClass.extend($, {

		/**
		 * Object animation
		 * @param poroperies - object
		 *		string: type:cssName[from;to]duration&direction
		 *			"from" | "to" - in pixels or percent
		 *		object: callback
		 *			onstart, onstep, onend
		 */
		animate: function() {

			var $SELF = this;

			// add properties
			$.foreach(arguments, function(args, i) {

				var prop = args[i], rule, prepareCallback = {}

				if (prop.callback) {
					prepareCallback = prop.callback;
					rule = prop.act;
				}
				else
					rule = prop;

				// set callback
				var callback = jClass.copy(
					// defaults
					{
						onstart:$.blank(),
						onend:$.blank()
					},
					// defined
					prepareCallback
				);

				// parse rule
				var data = rule.match(/^((\w+):)?(\w+)(\[(([\w%]+);)?([\w%]+)\])?(\d+)?(&(\w+))?$/);

				if (data) {

					var param = data.object(["type","name","from","to","duration","dir"],[2,3,6,7,8,10]);

					// on start
					callback.onstart();

					// set delta
					var delta = animationTypes[param.type] || animationTypes.line;
					// set processor
					var processor = param.dir && new Direction(delta)[param.dir] || delta;
					// convert numbers
					param.to = param.to.intval();
					param.duration = $.empty(param.duration) ? 1000 : param.duration.intval();
					param.from = ($.empty(param.from) ? $SELF.css(param.name) : param.from).intval();
					// set start time
					var start = $.time();

					// create animator
					var animator = callback.onstep || new Animator(param.name).run;

					// difference
					var diff = param.to - param.from;

					// put in rotation
					$SELF.buffer(function() {

						var progress = ($.time() - start) / param.duration; // animation progress

						// continue
						if (progress < 1) {
							// animation step
							animator(
								Math.round(processor(progress) * diff + param.from),
								$SELF
							);
							return true;
						}
						// break
						else {
							animator(param.to, $SELF);
							callback.onend();
							return false;
						}
					});

				}
				// end of callback
			});

			return $SELF;
		},

		/**
		 * Buffered rotation
		 * @param function
		 * @param arguments
		 */
		buffer: function(func, args) {

			// save in pool
			pool.garbage['H' + Number.random()] = {
				callback: func,
				a: args
			};
			
			pool.count++;

			// do start
			if (threadHandle == null) {

				threadHandle = setInterval(
					// callback
					function() {

						for(var h in pool.garbage) {

							var f = pool.garbage[h];

							if ( ! f.callback(f.a)) {
								delete pool.garbage[h];
								pool.count--;
							}
						}

						if (pool.count < 1) {
							clearInterval(threadHandle);
							threadHandle = null;
						}
					},
					// timeout
					50
				)
			}

			return this;
		}
	});

})();

// end of file